/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Robert Millan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <pwd.h>

#include "unet/ipc.h"
#include "unet/rpc.h"
#include "unet/client.h"
#include "unet/ip.h"
#include "unet/routes.h"
#include "unet/loop.h"
#include "unet/pcap.h"

struct send_packet_args
{
  uint8_t *packet;
  uint32_t len;
};
static void *
send_packet (void *a)
{
  struct send_packet_args *args = a;
  unet_ip_send_packet (args->packet, args->len);
  free (args);
  return NULL;
}

/* This routine is started as a thread for each new process that
   connects with us via IPC.  Its purpose is to dispatch requests
   from the UserNet clients.  */
struct dispatcher_thread_args
{
  int fd;
  pid_t pid;
  uid_t uid;
};
static void *
dispatcher_thread (void *arg)
{
  struct unet_rpc_req req;
  int status;
  struct dispatcher_thread_args *a = arg;
  ssize_t len;

  struct unet_rpc_adv adv =
    {
      .signature = UNET_SIGNATURE,
      .server_abi = { .max = UNET_MAX_SERVER_ABI, .min = UNET_MIN_SERVER_ABI },
    };

  debug_printf ("rpc", "\nAttending new IPC connection\n");

  if (unet_ipc_write (a->fd, &adv, sizeof (adv)) != sizeof (adv))
    {
      error (0, errno, "error sending abi advertisement");
      goto end;
    }

  len = unet_ipc_read (a->fd, &req, sizeof (req));
  if (len != sizeof (req))
    {
      error (0, errno, "error receiving request from client (read: %zd, expected: %zu)",
	     len, sizeof (req));
      goto end;
    }

  if (req.signature != UNET_SIGNATURE)
    {
      error (0, 0, "malformed request");
      goto end;
    }

  if (req.abi > UNET_MAX_SERVER_ABI
#if UNET_MIN_SERVER_ABI > 0
      || req.abi < UNET_MIN_SERVER_ABI
#endif
      )
    {
      error (0, 0, "server does not implement requested ABI (%u)", req.abi);
      goto end;
    }

  switch (req.type)
    {
    case UNET_RPC_INIT:
      /* Initialize receive channel.  */
      {
	struct unet_rpc_ack ack;
	memset (&ack, 0, sizeof (ack));

	struct unet_client *client = unet_client_add (a->fd, a->pid);
	if (! client)
	  {
	    ack.status = errno;
	    goto send_ack;
	  }

      send_ack:
	unet_ipc_write (a->fd, &ack, sizeof (ack));

	if (ack.status != 0)
	  break;

	while (1)
	  {
	    uint8_t packet[65536];
	    size_t len;
	    len = read (a->fd, packet, sizeof (packet));

	    if (len == -1)
	      error (1, errno, "read");

	    if (len == 0)
	      /* Channel was closed.  */
	      break;

	    struct send_packet_args *args = malloc (sizeof (*args));
	    args->packet = packet;
	    args->len = len;
	    pthread_t thread;
	    pthread_create (&thread, NULL, send_packet, args);
	  }

	unet_client_delete (client);
      }
      break;

    case UNET_RPC_PORT:
      /* Perform requested action.  */
      status = unet_port (a->fd, a->pid, a->uid,
			  req.data.port.type,
			  req.data.port.op,
			  req.data.port.listen_addr,
			  &req.data.port.num);

      /* Send acknowledgement to client.  */
      {
	struct unet_rpc_port_ack ack;
	memset (&ack, 0, sizeof (ack));
	ack.status = status;
	ack.num = req.data.port.num;
	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_COUNT_DEVICES:
      {
	struct unet_rpc_count_ack ack;
	memset (&ack, 0, sizeof (ack));
	ack.count = unet_device_count ();
	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_ROUTE_COUNT:
      {
	struct unet_rpc_count_ack ack;
	memset (&ack, 0, sizeof (ack));
	ack.count = unet_count_routes ();
	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_GET_DEVICE_INFO:
      {
	struct unet_rpc_get_device_info_ack ack;
	memset (&ack, 0, sizeof (ack));

	struct unet_device *device = unet_device_find (req.data.get_device_info.id);

	if (device)
	  memcpy (&ack.device, device, sizeof (*device));
	else
	  ack.status = ENODEV;

	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_ROUTE_GET:
      {
	struct unet_rpc_route_get_ack ack;
	memset (&ack, 0, sizeof (ack));

	struct unet_route *route = unet_get_route (req.data.route_get);

	if (route)
	  memcpy (&ack.route, route, sizeof (*route));
	else
	  ack.status = ENOENT;

	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_SET_DEVICE_INFO:
      {
	struct unet_rpc_ack ack;
	memset (&ack, 0, sizeof (ack));

	if (a->uid == 0)
	  {
	    struct unet_device *device = unet_device_find (req.data.set_device_info.device.id);
	    if (device)
	      {
		// FIXME: root clients are allowed to overwrite internal data
		memcpy (device, &req.data.set_device_info.device, sizeof (*device));
		ack.status = 0;
	      }
	    else
	      ack.status = ENODEV;
	  }
	else
	  ack.status = EPERM;

	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_ROUTE_ADD:
      {
	struct unet_rpc_ack ack;
	memset (&ack, 0, sizeof (ack));

	if (a->uid == 0)
	  unet_route_add (&req.data.route);
	else
	  ack.status = EPERM;

	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    case UNET_RPC_ROUTE_DEL:
      {
	struct unet_rpc_ack ack;
	memset (&ack, 0, sizeof (ack));

	if (a->uid == 0)
	  unet_route_del (&req.data.route);
	else
	  ack.status = EPERM;

	if (unet_ipc_write (a->fd, &ack, sizeof (ack)) != sizeof (ack))
	  error (0, errno, "error writing to client");
      }
      break;

    default:
      printf ("request not understood\n");
    }

end:
  close (a->fd);
  free (a);
  return NULL;
}

static void
report_signal (int signum)
{
  error (0, 0, "caught signal %u (ignored)", signum);
}

int
main (int argc, char *argv[])
{
  /* getpwnam is not thread-safe, but we're in main () and none of
     our threads have been spawned yet.  */
  struct passwd *nobody = getpwnam ("nobody");

  /* SIGPIPE can happen due to faulty client interaction, don't
     abort server because of it, just report it instead.  */
  signal (SIGPIPE, report_signal);

  unet_loop_init ();

  int i;
  for (i = 1; i < argc; i++)
    if (unet_pcap_init (argv[i]) == -1)
      error (1, errno, "unet_pcap_init");

  int listenfd = unet_ipc_listen ();
  if (listenfd == -1)
    error (1, errno, "unet_ipc_listen");

  /* Drop privileges after all devices have been opened and our IPC
     channel is being listened through.  */
  setuid (nobody->pw_uid);

  while (1)
    {
      pthread_t thread;

      struct dispatcher_thread_args *a = malloc (sizeof (*a));
      if (! a)
	continue;

      a->fd = unet_ipc_accept (listenfd, &a->pid, &a->uid);
      if (a->fd == -1)
	{
	  free (a);
	  continue;
	}

      pthread_create (&thread,
		      NULL /* default attributes */,
		      dispatcher_thread, a);
    }

  return 0;
}
